﻿using System;
using System.Collections.Generic;
using TuxedoLib;

namespace SpaceSquirrel
{
	public class PlayScene : GameSceneBase
	{
		private const int GOOD_HEALTH_COLOR = -1; // 255, 255, 255, 255
		private static readonly int POOR_HEALTH_COLOR = Draw.CreateColor(255, 128, 128);
		public const int BIKE_SPEED = 6;

		private const double JUMPING_VY = -14;
		private const double RUN_JUMPING_VY = -16;
		private const double WATER_JUMPING_VY = -5;

		public Sprite Player { get; set; }
		public string id;

		public object[] startArgs;
		public int updateCounter = 0;
		private Context context;

		public bool side;
		public int cols;
		public int rows;
		private TileTemplate[][] upper;
		private TileTemplate[][] lower;
		private Tile[][] tiles;
		private Image[] stars;
		private int runCounter;
		private string runCounterValidFor;
		public bool hasAtmosphere;
		private string bg;
		private Dictionary<string, List<Pair<string, string>>> doorSwaps;
		public int lazorCooldown;

		private Dictionary<string, string> overlayTriggers;
		public SpecialLevel[] special;
		private List<Sprite> sprites;

		public Tile[][] Tiles { get { return this.tiles; } }
		public Context Context { get { return this.context; } }
		public Dictionary<string, string> OverlayTriggers { get { return this.overlayTriggers; } }
		public List<Sprite> Sprites { get { return this.sprites; } }

		public PlayScene(string map, int startCol, int startRow, Context context)
			: base()
		{
			this.startArgs = new object[] { map, startCol, startRow };
			this.Flags = "M";
			this.context = context;
			string levelName = map.Split('.')[0];
			this.id = levelName;
			if (!map.EndsWith(".map"))
			{
				map += ".map";
			}
			MapParser mapParser = new MapParser(map);
			Map mapInstance = mapParser.Parse();
			this.hasAtmosphere = context.VolcanoA && context.VolcanoB && context.VolcanoC;

			this.bg = BackgroundHelper.GetBackground(levelName);
			List<Image> starsBuilder = new List<Image>();
			for (int i = 1; i <= 5; ++i)
			{
				starsBuilder.Add(ImageLibrary.GetImage("tiles/background/stars" + i + ".png"));
			}
			for (int i = 0; i < 10; ++i)
			{
				starsBuilder.Add(null);
			}
			this.stars = Util.MultiplyList<Image>(starsBuilder, 3);
			Util.ShuffleList<Image>(this.stars);
			this.runCounter = 0;
			this.runCounterValidFor = ":P";

			this.cols = mapInstance.Width;
			this.rows = mapInstance.Height;
			this.upper = mapInstance.Upper;
			this.lower = mapInstance.Lower;
			this.side = mapInstance.Side;

			this.lazorCooldown = 0;

			this.tiles = SquirrelUtil.MakeGrid<Tile>(this.cols, this.rows);

			this.doorSwaps = mapInstance.DoorSwaps;

			List<Pair<string, Tile>> doorTiles = new List<Pair<string, Tile>>();
			Dictionary<string, int[]> ladderTiles = new Dictionary<string, int[]>();
			List<Door> lowerDoors = new List<Door>();
			Tile t;
			for (int y = this.rows - 1; y >= 0; --y)
			{
				for (int x = 0; x < this.cols; ++x)
				{
					t = Tile.Make(this.lower[x][y], this.upper[x][y], x, y);
					this.tiles[x][y] = t;
					if (t.isDoor)
					{
						doorTiles.Add(new Pair<string, Tile>(x + "|" + y, t));
						if (this.side && y + 1 < this.rows)
						{
							// add the tile below the door to the potential door tiles
							// the door list from the map parser will check for the tile below a door iff it's a tall door and is 
							// expecting to find it in this list. If not, then this gets safely ignored
							doorTiles.Add(new Pair<string, Tile>(x + "|" + (y + 1), this.tiles[x][y + 1]));
						}
					}

					if (t.isLadder)
					{
						ladderTiles[x + "|" + y] = new int[] { x, y };
					}
				}
			}

			foreach (string lk in ladderTiles.Keys)
			{
				int[] coord = ladderTiles[lk];
				int x = coord[0];
				int y = coord[1];
				if (y > 0)
				{
					if (!ladderTiles.ContainsKey(x + "|" + (y - 1)))
					{
						this.tiles[x][y].isTop = true;
					}
				}
			}

			Dictionary<string, Door> doorLookup = new Dictionary<string, Door>();
			foreach (Door door in mapInstance.Doors)
			{
				List<int[]> xys = new List<int[]>() { new int[] { door.SX, door.SY } };
				if (this.side)
				{
					xys.Add(new int[] { door.SX, door.SY + 1 });
				}
				foreach (int[] xy in xys)
				{
					doorLookup[xy[0] + "|" + xy[1]] = door;
				}
			}

			foreach (Pair<string, Tile> doorTile in doorTiles)
			{
				string dk = doorTile.First;
				Door door = doorLookup.ContainsKey(dk) ? doorLookup[dk] : null;
				if (door != null)
				{
					doorTile.Second.door = door;

					// if there's a door, completely ignore any blocking stuff
					doorTile.Second.collisions = new int[0][];
					doorTile.Second.solid = false;
				}
			}

			this.Player = new Sprite("player_" + (this.side ? "side" : "over"), startCol * 16 + 8, startRow * 16 + 7);
			this.sprites = new List<Sprite>() { this.Player };

			if (levelName == "bike_level")
			{
				this.Player.bikeMode = true;
				this.Player.floats = true;
			}

			foreach (EnemySeed enemy in mapInstance.Enemies)
			{
				Sprite sprite = new Sprite(enemy.ID, enemy.Col * 16 + 8, enemy.Row * 16 + 8);
				
				// TODO: This may not be necessary:
				sprite.isEnemy = true;

				this.sprites.Add(sprite);
			}

			this.overlayTriggers = mapInstance.OverlayTriggers;

			foreach (PowerUp powerup in mapInstance.Powerups)
			{
				string puid = powerup.ID;
				if (context.PowerUpsTaken.ContainsKey(puid))
				{
					Sprite powerupSprite = new Sprite("powerup", powerup.Col * 16 + 8, powerup.Row * 16 + 7);
					powerupSprite.powerUpInfo = powerup;
					this.sprites.Add(powerupSprite);
				}
			}

			this.special = SpecialLevel.GetSpecialLevelStuff(levelName, this);
			foreach (SpecialLevel special in this.special)
			{
				special.PostInit();
			}
		}

		public void PlayMusic()
		{
			JukeBox.PlaySongForLevelMaybe(this.id);
		}


		private Tile PlayersTile() { return this.PlayersTile(0, 0); }

		private Tile PlayersTile(int offsetX, int offsetY)
		{
			Sprite p = this.Player;
			if (p == null) return null;
			int tx = (int)(p.modelX / 16) + offsetX;
			int ty = (int)(p.modelY / 16) + offsetY;
			if (tx >= 0 && ty >= 0 && tx < this.cols && ty < this.rows)
			{
				return this.tiles[tx][ty];
			}
			return null;
		}

		private bool shooting;

		public override void ProcessInput(List<MyEvent> events, Dictionary<string, bool> pressedActions)
		{
			bool frozen = false;
			foreach (SpecialLevel s in this.special)
			{
				if (s.freeze)
				{
					frozen = true;
				}
			}

			this.shooting = this.lazorCooldown > 0;

			if (!frozen)
			{
				if (this.side)
				{
					double dx = 0;
					double dy = 0;
					double v;
					bool running = false;
					if (this.context.Gravity)
					{
						if (pressedActions["B"] && !this.Player.cling)
						{
							running = true;
							v = 5;
						}
						else
						{
							v = 3;
						}
					}
					else
					{
						v = 2.5;
					}

					if (this.Player.bikeMode)
					{
						v = 2.0;
					}

					if (pressedActions["left"])
					{
						dx = -v;
						if (running && this.runCounterValidFor == "left")
						{
							this.runCounter++;
						}
						else
						{
							this.runCounter = 0;
							this.runCounterValidFor = "left";
						}
					}
					else if (pressedActions["right"])
					{
						dx = v;
						if (running && this.runCounterValidFor == "right")
						{
							this.runCounter++;
						}
						else
						{
							this.runCounter = 0;
							this.runCounterValidFor = "right";
						}
					}
					else
					{
						this.runCounter = 0;
						this.runCounterValidFor = "nothing";
					}

					if (pressedActions["up"])
					{
						if (this.Player.bikeMode)
						{
							dy = -v;
						}

						Tile pt = this.PlayersTile();
						if (pt != null && pt.isLadder)
						{
							this.Player.cling = true;
							this.Player.ladderDY = -2;
						}
					}
					else if (pressedActions["down"])
					{
						if (this.Player.bikeMode)
						{
							dy = v;
						}

						Tile pt = this.PlayersTile();
						if (pt != null && pt.isLadder)
						{
							this.Player.ladderDY = 2;
						}
						else
						{
							pt = this.PlayersTile(0, 1);
							if (pt != null && pt.isLadder)
							{
								this.Player.modelY += 8;
								this.Player.ladderDY = 2;
								this.Player.cling = true;
							}
						}
					}

					if (this.Player != null)
					{
						if (this.Player.bikeMode)
						{
							dx += BIKE_SPEED;
						}
						this.Player.dx = dx;
						this.Player.dy = dy;
					}

					foreach (MyEvent ev in events)
					{
						if (ev.Action == "A")
						{
							if (ev.Down)
							{
								Tile pt = this.PlayersTile();
								if (this.Player.onGround || this.Player.cling || (pt != null && pt.isWater))
								{
									this.Player.onGround = false;
									this.Player.cling = false;
									this.Player.ladderDY = 0;
									if (pt.isWater)
									{
										this.Player.vy += WATER_JUMPING_VY;
										SoundManager.PlayNoise("swim");
									}
									else
									{
										this.Player.vy = JUMPING_VY;
										if (this.runCounter > 5 && this.context.Gravity)
										{
											this.Player.vy = RUN_JUMPING_VY;
										}
										SoundManager.PlayNoise("jump");
									}
								}
							}
							else
							{
								if (this.Player.vy < 0)
								{
									this.Player.vy = this.Player.vy / 4.0; // maybe set to 0 instead?
								}
							}
						}
						else if (ev.Action == "B" && ev.Down) // SHOOT UR LAZOR PEW PEW!!!!1
						{
							if (this.lazorCooldown <= 0)
							{
								Sprite p = this.Player;
								bool lazorLeft = p.lastDirection == "left";
								if (p.damageDir != null)
								{
									lazorLeft = this.Player.damageDir == "left";
								}
								int lazorVX = lazorLeft ? -8 : 8;
								int x = p.x + lazorVX;
								int y = p.y - 8;

								if (p.spawns == null)
								{
									p.spawns = new List<Sprite>();
								}

								SoundManager.PlayNoise("lazor");
								Sprite bullet = new Sprite("lazor", x, y);
								bullet.bvx = lazorVX;
								bullet.isBullet = true;
								bullet.ghost = true;
								bullet.floats = true;
								p.spawns.Add(bullet);
								this.lazorCooldown = 6;
							}
							else if (ev.Action == "start" && ev.Down)
							{
								this.Next = new PauseScene(this);
							}
						}
					}
				}
				else
				{
					double v = 3;
					double dx = 0;
					double dy = 0;

					if (pressedActions["left"])
					{
						dx = -v;
					}
					else if (pressedActions["right"])
					{
						dx = v;
					}

					if (pressedActions["up"])
					{
						dy = -v;
					}
					else if (pressedActions["down"])
					{
						dy = v;
					}

					foreach (MyEvent ev in events)
					{
						if (ev.Action == "start" && ev.Down)
						{
							this.Next = new PauseScene(this);
						}
					}

					this.Player.dx = dx;
					this.Player.dy = dy;
				}
			}
		}

		public override void Update()
		{
			this.updateCounter++;
			this.PlayMusic();

			double playerX = this.Player.modelX;
			double playerY = this.Player.modelY;
			List<Sprite> sprites = this.sprites;
			this.lazorCooldown--;
			List<Sprite> newSprites = new List<Sprite>();
			List<Sprite> allBullets = new List<Sprite>();
			for (int i = 0; i < this.sprites.Count; ++i)
			{
				Sprite sprite = this.sprites[i];
				if (sprite != null && !sprite.dead)
				{
					sprite.Update(this);
					if (sprite.isEnemy && this.Player != null)
					{
						if (sprite.IsCollision(this.Player))
						{
							this.PlayerHit(1);
						}
					}

					if (sprite.spawns != null)
					{
						foreach (Sprite spawn in sprite.spawns)
						{
							sprites.Add(spawn);
						}
						sprite.spawns = null;
					}

					if (sprite.isBullet)
					{
						allBullets.Add(sprite);
					}
				}
			}

			for (int i = 0; i < sprites.Count; ++i)
			{
				Sprite sprite = sprites[i];
				if (allBullets.Count > 0 && sprite.type != "bird1" && sprite.type != "bird2")
				{
					if (sprite.isEnemy)
					{
						int left = sprite.x - 8;
						int right = sprite.x + 8;
						int bottom = sprite.y + 8;
						int top = bottom - sprite.height;

						for (int j = 0; j < allBullets.Count; ++j)
						{
							Sprite bullet = allBullets[j];
							int bleft = bullet.x - 8;
							int bright = bleft + 16;
							int bbottom = bullet.y + 4;
							int btop = bbottom - 8;

							if (bleft > right) { }
							else if (bright < left) { }
							else if (btop > bottom) { }
							else if (bbottom < top) { }
							else
							{
								sprite.dead = true;
								bullet.dead = true;
								string spriteType = sprite.type;
								if (spriteType == "acorn" || spriteType == "acorntop" || spriteType == "byat" || spriteType == "moonsquid" || spriteType == "lavamonster")
								{
									int dropN = Util.RandomInt(0, 200);
									string drop = null;
									if (dropN == 0)
									{
										drop = "1up";
									}
									else if (dropN < 8)
									{
										drop = "life_big";
									}
									else if (dropN < 30)
									{
										drop = "life_small";
									}

									if (drop != null)
									{
										// Can't do spawns since it'll get filtered out before then.
										Sprite powerup = new Sprite("powerup", sprite.x, sprite.y - 1);
										PowerUp info = new PowerUp()
										{
											 ID = null,
											 Type = drop
										};
										powerup.powerUpInfo = info;
										newSprites.Add(powerup);
									}
								}

								string poofType = "poof_small";
								if (spriteType == "lavamonster")
								{
									poofType = "poof_big";
								}
								Sprite poof = new Sprite(poofType, sprite.x, sprite.y);
								newSprites.Add(poof);
								SoundManager.PlayNoise("enemy_dies");
							}
						}
					}
				}

				if (!sprite.dead)
				{
					newSprites.Add(sprite);
				}
			}

			this.sprites = newSprites;

			int playerTX = (int)(this.Player.modelX / 16);
			int playerTY = (int)(this.Player.modelY / 16);
			Tile activeTile = this.tiles[playerTX][playerTY];
			if (activeTile.door != null)
			{
				Door door = activeTile.door;
				string target = door.Target;
				if (this.doorSwaps.ContainsKey(target))
				{
					Context ctx = this.context;
					foreach (Pair<string, string> swap in this.doorSwaps[target])
					{
						string trigger = swap.First;
						if (
							(trigger == "gravity" && ctx.Gravity) ||
							(trigger == "lavaA" && ctx.VolcanoA) ||
							(trigger == "lavaB" && ctx.VolcanoB) ||
							(trigger == "waterA" && ctx.BalloonA) ||
							(trigger == "waterB" && ctx.BalloonB) ||
							(trigger == "waterC" && ctx.BalloonC) ||
							(trigger == "waterD" && ctx.BalloonD))
						{
							target = swap.Second;
							break;
						}
					}
				}

				foreach (SpecialLevel special in this.special)
				{
					if (special.HasDoorTrigger)
					{
						target = special.DoorTrigger(target);
						break;
					}
				}

				if (target != null)
				{
					this.Next = new PlayScene(target, door.TX, door.TY, this.context);
				}
			}

			Tile pUp = this.PlayersTile(0, -1);
			Tile pDown = this.PlayersTile(0, 0);

			if ((pUp != null && pUp.isSpike) || (pDown != null && pDown.isSpike))
			{
				this.PlayerHit(3);
			}

			if ((pUp != null && pUp.isLava) || (pDown != null && pDown.isLava))
			{
				this.Next = new DeathOverrideScene(this, "lava");
			}

			foreach (SpecialLevel special in this.special)
			{
				special.Update();
			}
		}

		// default value was 1
		private void PlayerHit(int amount)
		{
			if (this.Player.blinkCounter < 0)
			{
				this.Player.Hit(this, amount);
			}
		}

		public bool IsCollision(double pLeft, double pTop, double pRight, double pBottom)
		{
			if (pLeft < 0) return true;
			if (pTop < 0) return true;
			int tLeft = (int)(pLeft / 16);
			int tRight = pRight == pLeft ? tLeft : (int)(pRight / 16);
			int tTop = (int)(pTop / 16);

			// potentially a bug
			// bottom row of sprite is technically top row of ground below. This intersection should be ignored.
			int tBottom = (int)((pBottom - 3) / 16);

			if (tLeft < 0) return true;
			if (tTop < 0) return true;
			if (tRight >= this.cols) return true;
			if (tBottom >= this.rows) return true;

			int x;
			for (int y = tTop; y <= tBottom; ++y)
			{
				for (x = tLeft; x <= tRight; ++x)
				{
					if (this.tiles[x][y].solid)
					{
						return true;
					}
				}
			}

			return false;
		}

		private void RenderOverlay(Image screen)
		{
			Draw.RectangleFill(screen, Draw.CreateColor(0, 0, 0), 0, 0, 256, 8);
			screen.Blit(ImageLibrary.GetImage("misc/lives.png"), 0, 0);
			Image txt = Text.Render(-1, "x" + this.context.Lives);
			screen.Blit(txt, 8, 0);

			int x = 112;
			txt = Text.Render(-1, "HEALTH");
			screen.Blit(txt, x, 0);

			x += txt.Width + 8;

			int health = this.context.LifeMeter;
			int color = GOOD_HEALTH_COLOR;
			if (health < 4)
			{
				color = POOR_HEALTH_COLOR;
			}

			for (int i = 0; i < 5; ++i)
			{
				Draw.RectangleFill(screen, Draw.CreateColor(128, 128, 128), x, 0, 16, 8);
				if (health >= ((i + 1) * 2))
				{
					Draw.RectangleFill(screen, color, x + 1, 1, 14, 6);
				}
				else if (health == 2 * i + 1)
				{
					Draw.RectangleFill(screen, color, x + 1, 1, 7, 6);
				}
				x += 16;
			}

			Pair<char, int> cat = this.context.GetCurrentCountAndType();
			if (cat != null)
			{
				int count = cat.Second;
				int total;
				char type = cat.First;
				Image img;
				if (type == 'W')
				{
					img = ImageLibrary.GetImage("misc/water_icon.png");
					total = 4;
				}
				else
				{
					img = ImageLibrary.GetImage("misc/volcano_icon.png");
					total = 3;
				}

				screen.Blit(img, 48, 0);
				screen.Blit(Text.Render(-1, count + "/" + total), 56, 0);
			}
		}

		public override void Render(Image screen, int renderCounter)
		{
			if (this.bg == "stars")
			{
				screen.Fill(0, 0, 0);
			}
			else if (this.bg == "cave")
			{

			}
			else if (this.bg == "volcano")
			{

			}
			else
			{
				screen.Fill(0, 0, 40); // sky
			}

			int colStart = 0;
			int colEnd = this.cols - 1;
			int rowStart = 0;
			int rowEnd = this.rows - 1;

			int offsetX = -(this.Player.x - 128);
			int offsetY = -(this.Player.y - 112);

			if (this.Player.bikeMode)
			{
				offsetX = -(this.updateCounter * BIKE_SPEED);
			}

			if (offsetX > 0) offsetX = 0;
			if (offsetY > 0) offsetY = 0;

			int right = -(this.cols * 16 - 256);
			if (offsetX < right) offsetX = right;

			int bottom = -(this.rows * 16 - 224);
			if (offsetY < bottom) offsetY = bottom;

			if (this.cols * 16 < 256)
			{
				offsetX = (256 - this.cols * 16) / 2;
			}
			if (this.rows * 16 < 224)
			{
				offsetY = (224 - this.rows * 16) / 2;
			}

			Image cave = ImageLibrary.GetImage("tiles/background/cave.png");
			Image volcanoBg = ImageLibrary.GetImage("tiles/background/volcano.png");
			int starLen = this.stars.Length;
			Image[] stars = this.stars;
			int counter = 0;

			int idealColStart = -(int)(offsetX / 16) - 1;
			if (idealColStart > colStart)
			{
				colStart = idealColStart;
			}

			int idealColEnd = colStart + 17;
			if (idealColEnd < colEnd)
			{
				colEnd = idealColEnd;
			}

			int idealRowStart = -(int)(offsetY / 16) - 1;
			if (idealRowStart > rowStart)
			{
				rowStart = idealRowStart;
			}

			int idealRowEnd = rowStart + 15;
			if (idealRowEnd < rowEnd)
			{
				rowEnd = idealRowEnd;
			}

			int lavaLevel = 0;
			int shake = 0;
			foreach (SpecialLevel s in this.special)
			{
				lavaLevel = Math.Max(lavaLevel, s.lavaLevel);
				shake = Math.Max(shake, s.shakeScreen ? 3 : 0);
			}

			offsetX += shake;
			offsetY += shake / 3;

			string lavaRowType = null;

			int col, row, fromBottom, x, y;
			Image lavaImage = null;
			Image bgImage;
			for (row = rowStart; row <= rowEnd; ++row)
			{
				fromBottom = rowEnd - row + 1;
				if (lavaLevel > 0)
				{
					if (fromBottom == lavaLevel)
					{
						lavaRowType = "upper";
						int lavaCounter = ((int)((renderCounter / 4) % 4) + 1);
						lavaImage = ImageLibrary.GetImage("tiles/fluids/lava" + lavaCounter + ".png");
					}
					else if (fromBottom < lavaLevel)
					{
						lavaRowType = "inner";
						lavaImage = ImageLibrary.GetImage("tiles/fluids/lava.png");
					}
				}

				for (col = colStart; col <= colEnd; ++col)
				{
					counter++;
					x = col * 16 + offsetX;
					y = row * 16 + offsetY;
					if (this.bg == "cave")
					{
						screen.Blit(cave, x, y);
					}
					else if (this.bg == "volcano")
					{
						screen.Blit(volcanoBg, x, y);
						if (lavaRowType != null)
						{
							screen.Blit(lavaImage, x, y);
						}
					} /////////////////// YOU LEFT OFF SOMEWHERE AROUND HERE
					else if (this.bg == "stars")
					{
						bgImage = stars[(col + this.rows * row + row * row) % starLen];
						if (bgImage != null)
						{
							screen.Blit(bgImage, x, y);
						}
					}

					TileTemplate[] templates = this.tiles[col][row].templates;
					for (int i = 0; i < templates.Length; ++i)
					{
						Image img = templates[i].GetImage(renderCounter);
						screen.Blit(img, x, y);
					}
				}
			}

			List<int[]> reRender = new List<int[]>();
			int adjustedRenderCounter = renderCounter / 4;
			foreach (Sprite sprite in this.sprites)
			{
				if (sprite != null && !sprite.dead)
				{
					sprite.Render(this, screen, offsetX, offsetY, adjustedRenderCounter);
					if (sprite.inBackground)
					{
						int tx = sprite.x / 16;
						int ty = (sprite.y + 8) / 16;
						reRender.Add(new int[] { tx, ty });
						reRender.Add(new int[] { tx, ty - 1 });
						reRender.Add(new int[] { tx, ty - 2 });
					}
				}
			}

			foreach (int[] rr in reRender)
			{
				col = rr[0];
				row = rr[1];
				if (row >= 0)
				{
					x = col * 16 + offsetX;
					y = row * 16 + offsetY;
					TileTemplate[] templates = this.tiles[col][row].templates;
					for (int i = 0; i < templates.Length; ++i)
					{
						Image img = templates[i].GetImage(renderCounter);
						screen.Blit(img, x, y);
					}
				}
			}

			foreach (SpecialLevel s in this.special)
			{
				if (s.HasRender)
				{
					s.Render(screen, renderCounter, offsetX, offsetY);
				}
			}

			this.RenderOverlay(screen);
		}

		/// <summary>
		/// Default value for slightDelay was false
		/// </summary>
		public void TriggerDialog(string id, bool slightDelay)
		{
			this.Next = new DialogScene(this, id, slightDelay, false, false);
		}
	}
}
